package com.panwrona.downloadprogressbar.library;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Arc;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.app.dispatcher.TaskDispatcher;
import ohos.app.dispatcher.task.Revocable;

public class DownloadProgressBar extends Component implements Component.DrawTask{

    private static final String TAG = DownloadProgressBar.class.getSimpleName();
    private Context mContext;
    private static final int DEFAULT_PROGRESS_DURATION = 1000;
    private static final int DEFAULT_RESULT_DURATION = 1000;
    private static final float DEFAULT_OVERSHOOT_VALUE = 2.5f;
    private static int DEFAULT_VALUE = 0;
    private static Color DEFAULT_COLOR = Color.BLACK;

    private Paint mCirclePaint;
    private Paint mDrawingPaint;
    private Paint mProgressPaint;
    private Paint mProgressBackgroundPaint;

    private float mRadius;
    private float mStrokeWidth;
    private float mLineWidth;
    private float mLengthFix;
    private float mArrowLineToDotAnimatedValue;
    private float mArrowLineToHorizontalLineAnimatedValue;
    private float mDotToProgressAnimatedValue;
    private float mCurrentGlobalProgressValue;
    private float mSuccessValue;
    private float mExpandCollapseValue =0;
    private float mErrorValue;
    private float mOvershootValue;

    private float mCenterX;
    private float mCenterY;
    private float mPaddingX;
    private float mPaddingY;

    private Color mCircleBackgroundColor;
    private Color mDrawingColor;
    private Color mProgressBackgroundColor;
    private Color mProgressColor;
    private int mProgressDuration;
    private int mResultDuration;

    private AnimatorGroup mArrowToLineAnimatorSet;
    private AnimatorGroup mProgressAnimationSet;

    private ValueAnimator mDotToProgressAnimation;
    private ValueAnimator mProgressAnimation;
    private ValueAnimator mSuccessAnimation;
    private ValueAnimator mExpandAnimation;
    private ValueAnimator mCollapseAnimation;
    private ValueAnimator mErrorAnimation;
    private ValueAnimator mArrowLineToDot;
    private ValueAnimator mArrowLineToHorizontalLine;
    private ValueAnimator mManualProgressAnimation;

    private RectFloat mCircleBounds;
    private RectFloat mProgressBackgroundBounds = new RectFloat();
    private RectFloat mProgressBounds = new RectFloat();

    private OnProgressUpdateListener mOnProgressUpdateListener;
    private AnimatorGroup mManualProgressAnimationSet;
    private float mFromArc = 0;
    private float mToArc = 0;
    private float mCurrentGlobalManualProgressValue;

    private enum State {ANIMATING_LINE_TO_DOT, IDLE, ANIMATING_SUCCESS, ANIMATING_ERROR, ANIMATING_PROGRESS, ANIMATING_MANUAL_PROGRESS}

    private State mState;
    private State mResultState;
    private State mWhichProgress;

    public DownloadProgressBar(Context context) {
        super(context);
        mContext = context;
    }

    public DownloadProgressBar(Context context, AttrSet attrs) {
        super(context, attrs);
        mContext = context;
        initAttrs(context, attrs);
        init();
        addDrawTask(this::onDraw);
    }

    private void initAttrs(Context context, AttrSet attrset) {


        mRadius = attrset.getAttr("circleRadius").isPresent() ?
                attrset.getAttr("circleRadius").get().getDimensionValue(): DEFAULT_VALUE;

        mStrokeWidth = attrset.getAttr("strokeWidth").isPresent() ?
                attrset.getAttr("strokeWidth").get().getDimensionValue() : DEFAULT_VALUE;

        mLineWidth = attrset.getAttr("lineWidth").isPresent() ?
                attrset.getAttr("lineWidth").get().getDimensionValue() : DEFAULT_VALUE;
        mLengthFix = (float) (mLineWidth / (2* Math.sqrt(2)));

        mProgressDuration = attrset.getAttr("progressDuration").isPresent() ?
                attrset.getAttr("progressDuration").get().getIntegerValue() : DEFAULT_PROGRESS_DURATION;

        mResultDuration = attrset.getAttr("resultDuration").isPresent() ?
                attrset.getAttr("resultDuration").get().getIntegerValue() : DEFAULT_RESULT_DURATION;

        mProgressBackgroundColor = attrset.getAttr("progressBackgroundColor").isPresent() ?
                attrset.getAttr("progressBackgroundColor").get().getColorValue(): DEFAULT_COLOR;

        mDrawingColor = attrset.getAttr("drawingColor").isPresent() ?
                attrset.getAttr("drawingColor").get().getColorValue() : DEFAULT_COLOR;

        mProgressColor = attrset.getAttr("progressColor").isPresent() ?
                attrset.getAttr("progressColor").get().getColorValue() : DEFAULT_COLOR;

        mCircleBackgroundColor = attrset.getAttr("circleBackgroundColor").isPresent() ?
                attrset.getAttr("circleBackgroundColor").get().getColorValue() : DEFAULT_COLOR;

        mOvershootValue = attrset.getAttr("overshootValue").isPresent() ?
                attrset.getAttr("overshootValue").get().getFloatValue() : DEFAULT_OVERSHOOT_VALUE;
    }

    private void init() {
        mCirclePaint = new Paint();
        mCirclePaint.setAntiAlias( true);
        mCirclePaint.setStyle(Paint.Style.STROKE_STYLE);
        mCirclePaint.setColor(mCircleBackgroundColor);
        mCirclePaint.setStrokeWidth(mStrokeWidth);

        mDrawingPaint = new Paint();
        mDrawingPaint.setAntiAlias( true);
        mDrawingPaint.setStyle(Paint.Style.STROKE_STYLE);
        mDrawingPaint.setColor(mDrawingColor);
        mDrawingPaint.setStrokeWidth(mLineWidth);

        mProgressPaint = new Paint();
        mProgressPaint.setAntiAlias( true);
        mProgressPaint.setColor(mProgressColor);
        mProgressPaint.setStyle(Paint.Style.FILL_STYLE);

        mProgressBackgroundPaint = new Paint();
        mProgressBackgroundPaint.setAntiAlias( true);
        mProgressBackgroundPaint.setColor(mProgressBackgroundColor);
        mProgressBackgroundPaint.setStyle(Paint.Style.FILL_STYLE);

        mState = State.IDLE;
        setupAnimations();
    }

    private void setLayout() {
        int w =getWidth();
        int h = getHeight();

        mCenterX = w / 2f;
        mCenterY = h / 2f;
        mPaddingX = w/ 2f - mRadius;
        mPaddingY = h / 2f - mRadius;

        mCircleBounds = new RectFloat();
        mCircleBounds.top = mPaddingY;
        mCircleBounds.left = mPaddingX;
        mCircleBounds.bottom = h / 2f + mRadius;
        mCircleBounds.right = w / 2f + mRadius;
    }

    private void setupAnimations() {
        mArrowLineToDot = ValueAnimator.ofFloat(0, mRadius / 4);
        mArrowLineToDot.setValueUpdateListener(new ValueAnimator.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                mArrowLineToDotAnimatedValue = value;
                invalidate();
            }
        });
        mArrowLineToDot.setDuration(200);
        mArrowLineToDot.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

                mState = State.ANIMATING_LINE_TO_DOT;
                if (mOnProgressUpdateListener != null) {
                    mOnProgressUpdateListener.onStarted();
                }
            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {

            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

        });
        mArrowLineToDot.setCurveType(Animator.CurveType.ACCELERATE);

        mArrowLineToHorizontalLine = ValueAnimator.ofFloat(0, mRadius / 2);
        mArrowLineToHorizontalLine.setValueUpdateListener(new ValueAnimator.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                mArrowLineToHorizontalLineAnimatedValue = value;
                invalidate();
            }
        });
        mArrowLineToHorizontalLine.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

        });
        mArrowLineToHorizontalLine.setDuration(600);
        mArrowLineToHorizontalLine.setDelay(400);
        mArrowLineToHorizontalLine.setCurveType(Animator.CurveType.OVERSHOOT);

        mDotToProgressAnimation = ValueAnimator.ofFloat(0, mRadius);
        mDotToProgressAnimation.setDuration(600);
        mDotToProgressAnimation.setDelay(600);
        mDotToProgressAnimation.setCurveType(Animator.CurveType.OVERSHOOT);
        mDotToProgressAnimation.setValueUpdateListener(new ValueAnimator.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                mDotToProgressAnimatedValue = value;
                invalidate();
            }
        });
        mDotToProgressAnimation.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {

                if(mWhichProgress == State.ANIMATING_PROGRESS)
                    mProgressAnimationSet.start();
                else if(mWhichProgress == State.ANIMATING_MANUAL_PROGRESS)
                    mManualProgressAnimationSet.start();

                mState = mWhichProgress;

            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

        });

        mArrowToLineAnimatorSet = new AnimatorGroup();
        mArrowToLineAnimatorSet.runParallel(mArrowLineToDot, mArrowLineToHorizontalLine, mDotToProgressAnimation);

        mProgressAnimation = ValueAnimator.ofFloat(0, 360f);
        mProgressAnimation.setDelay(500);
        mProgressAnimation.setCurveType(Animator.CurveType.LINEAR);
        mProgressAnimation.setValueUpdateListener(new ValueAnimator.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {

                mCurrentGlobalProgressValue = value;
                if (mOnProgressUpdateListener != null) {
                    mOnProgressUpdateListener.onProgressUpdate(mCurrentGlobalProgressValue);
                }
                invalidate();
            }
        });
        mProgressAnimation.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
                mDotToProgressAnimatedValue = 0;
            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

        });
        mProgressAnimation.setDuration(mProgressDuration);

        mManualProgressAnimation = ValueAnimator.ofFloat(mFromArc, mToArc);
        mManualProgressAnimation.setValueUpdateListener(new ValueAnimator.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                mCurrentGlobalManualProgressValue = value;
                invalidate();
            }
        });
        mManualProgressAnimation.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

                if(mOnProgressUpdateListener != null) {
                    mOnProgressUpdateListener.onManualProgressStarted();
                }
                mDotToProgressAnimatedValue = 0;
            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
                if(mOnProgressUpdateListener != null) {
                    mOnProgressUpdateListener.onManualProgressEnded();
                }
                if(mToArc > 359) {
                    mCollapseAnimation.start();
                }

            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

        });



        mExpandAnimation = ValueAnimator.ofFloat(0, mRadius / 6);
        mExpandAnimation.setDuration(300);
        mExpandAnimation.setCurveType(Animator.CurveType.DECELERATE);
        mExpandAnimation.setValueUpdateListener(new ValueAnimator.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                mExpandCollapseValue = v;
                invalidate();
            }
        });

        mCollapseAnimation = ValueAnimator.ofFloat(mRadius / 6, mStrokeWidth / 2);
        mCollapseAnimation.setDuration(300);
        mCollapseAnimation.setDelay(300);
        mCollapseAnimation.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

            }

            @Override
            public void onStop(Animator animator) {
                
            }

            @Override
            public void onEnd(Animator animator) {
                if(mState == State.ANIMATING_MANUAL_PROGRESS) {
                    if (mResultState == State.ANIMATING_ERROR) {
                        mErrorAnimation.start();
                    } else if (mResultState == State.ANIMATING_SUCCESS) {
                        mSuccessAnimation.start();
                    }
                }
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

        });
        mCollapseAnimation.setCurveType( Animator.CurveType.ACCELERATE_DECELERATE);
        mCollapseAnimation.setValueUpdateListener(new ValueAnimator.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                mExpandCollapseValue = value;
                invalidate();
            }

        });
        mManualProgressAnimationSet = new AnimatorGroup();
        mManualProgressAnimationSet.runSerially(mExpandAnimation, mManualProgressAnimation);

        mProgressAnimationSet = new AnimatorGroup();
        mProgressAnimationSet.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animation) {

            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onEnd(Animator animation) {
                if (mResultState == State.ANIMATING_ERROR) {
                    mErrorAnimation.start();
                } else if (mResultState == State.ANIMATING_SUCCESS) {
                    mSuccessAnimation.start();
                }
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }

            @Override
            public void onCancel(Animator animation) {

            }

        });
        mProgressAnimationSet.runSerially(mExpandAnimation, mProgressAnimation, mCollapseAnimation);

        mErrorAnimation = ValueAnimator.ofFloat(0, mRadius / 4);
        mErrorAnimation.setDuration(600);
        mErrorAnimation.setDelay(500);
        mErrorAnimation.setCurveType( Animator.CurveType.ACCELERATE_DECELERATE);
        mErrorAnimation.setValueUpdateListener(new ValueAnimator.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                mErrorValue = value;
                invalidate();
            }
        });
        mErrorAnimation.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

                mState = State.ANIMATING_ERROR;
                if (mOnProgressUpdateListener != null) {
                    mOnProgressUpdateListener.onAnimationError();
                }
            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {

                TaskDispatcher dispatcher = mContext.getUITaskDispatcher();
                Revocable revocable = dispatcher.delayDispatch(new Runnable(){
                    @Override
                    public void run() {
                        if (mOnProgressUpdateListener != null) {
                            mOnProgressUpdateListener.onEnded();
                        }
                        mState = State.IDLE;
                        resetValues();
                        invalidate();
                    }
                }, mResultDuration);
                revocable.revoke();
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }


        });

        mSuccessAnimation = ValueAnimator.ofFloat(0, mRadius / 4);
        mSuccessAnimation.setDuration(600);
        mSuccessAnimation.setDelay(500);
        mSuccessAnimation.setCurveType( Animator.CurveType.ACCELERATE_DECELERATE);
        mSuccessAnimation.setValueUpdateListener(new ValueAnimator.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                mSuccessValue = value;
                invalidate();
            }
        });
        mSuccessAnimation.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
                mState = State.ANIMATING_SUCCESS;
                if (mOnProgressUpdateListener != null) {
                    mOnProgressUpdateListener.onAnimationSuccess();
                }
            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
                TaskDispatcher dispatcher = mContext.getUITaskDispatcher();
                Revocable revocable = dispatcher.delayDispatch(new Runnable(){
                    @Override
                    public void run() {

                        if (mOnProgressUpdateListener != null) {
                            mOnProgressUpdateListener.onEnded();
                        }
                        mState = State.IDLE;
                        resetValues();
                        invalidate();
                    }
                }, mResultDuration);
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

        });

    }

    private void resetValues() {
        mArrowLineToDotAnimatedValue = 0;
        mArrowLineToHorizontalLineAnimatedValue = 0;
        mCurrentGlobalProgressValue = 0;
        mCurrentGlobalManualProgressValue = 0;
        mManualProgressAnimation.setFloatValues(0,0);
        mToArc = 0;
        mFromArc = 0;
        mSuccessValue =0;
    }

    private void drawing(Canvas canvas) {
        canvas.drawCircle(mCenterX, mCenterY, mRadius, mCirclePaint);

        LogUtil.info("DownloadProgressbar","Inside drawing and mState "+ mState);
        switch (mState) {
            case IDLE:
                canvas.drawLine(mCenterX, mCenterY - mRadius / 2, mCenterX, mCenterY + mRadius / 2, mDrawingPaint);
                canvas.drawLine(mCenterX - mRadius / 2, mCenterY, mCenterX + mLengthFix, mCenterY + mRadius / 2 + mLengthFix, mDrawingPaint);
                canvas.drawLine(mCenterX - mLengthFix, mCenterY + mRadius / 2 + mLengthFix, mCenterX + mRadius / 2, mCenterY, mDrawingPaint);
                break;
            case ANIMATING_LINE_TO_DOT:
                if (!mDotToProgressAnimation.isRunning()) {
                    canvas.drawLine(
                            mCenterX,
                            mCenterY - mRadius / 2 + mArrowLineToDotAnimatedValue * 2 - mStrokeWidth / 2,
                            mCenterX,
                            mCenterY + mRadius / 2 - mArrowLineToDotAnimatedValue * 2 + mStrokeWidth / 2,
                            mDrawingPaint
                    );
                }
                canvas.drawLine(
                        mCenterX - mRadius / 2 - mArrowLineToHorizontalLineAnimatedValue / 2,
                        mCenterY,
                        mCenterX + mLengthFix,
                        mCenterY + mRadius / 2 - mArrowLineToHorizontalLineAnimatedValue + mLengthFix,
                        mDrawingPaint
                );
                canvas.drawLine(
                        mCenterX - mLengthFix,
                        mCenterY + mRadius / 2 - mArrowLineToHorizontalLineAnimatedValue + mLengthFix,
                        mCenterX + mRadius / 2 + mArrowLineToHorizontalLineAnimatedValue / 2,
                        mCenterY,
                        mDrawingPaint
                );
                break;
            case ANIMATING_PROGRESS:
                float progress = ((mCenterX + mRadius / 2 + mArrowLineToHorizontalLineAnimatedValue / 2) - (mCenterX - mRadius / 2 - mArrowLineToHorizontalLineAnimatedValue / 2)) / 360f;

                mDrawingPaint.setStrokeWidth(mStrokeWidth);
                canvas.drawArc(mCircleBounds, new Arc( -90, mCurrentGlobalProgressValue, false), mDrawingPaint);

                mProgressBackgroundBounds.left = mCenterX - mRadius / 2 - mArrowLineToHorizontalLineAnimatedValue / 2;
                mProgressBackgroundBounds.top = mCenterY - mExpandCollapseValue;
                mProgressBackgroundBounds.right =  mCenterX + mRadius / 2 + mArrowLineToHorizontalLineAnimatedValue / 2;
                mProgressBackgroundBounds.bottom = mCenterY + mExpandCollapseValue;
                canvas.drawRoundRect(mProgressBackgroundBounds, 45, 45, mProgressBackgroundPaint);

                mProgressBounds.left = mCenterX - mRadius / 2 - mArrowLineToHorizontalLineAnimatedValue / 2;
                mProgressBounds.top = mCenterY - mExpandCollapseValue;
                mProgressBounds.right = mCenterX - mRadius / 2 - mArrowLineToHorizontalLineAnimatedValue / 2 + progress * mCurrentGlobalProgressValue;
                mProgressBounds.bottom = mCenterY + mExpandCollapseValue;
                canvas.drawRoundRect(mProgressBounds, 45, 45, mProgressPaint);
                break;
            case ANIMATING_MANUAL_PROGRESS:
                float manualProgress = ((mCenterX + mRadius / 2 + mArrowLineToHorizontalLineAnimatedValue / 2) - (mCenterX - mRadius / 2 - mArrowLineToHorizontalLineAnimatedValue / 2)) / 360f;

                mDrawingPaint.setStrokeWidth(mStrokeWidth);
                canvas.drawArc(mCircleBounds, new Arc(-90, mCurrentGlobalManualProgressValue, false), mDrawingPaint);

                mProgressBackgroundBounds.left = mCenterX - mRadius / 2 - mArrowLineToHorizontalLineAnimatedValue / 2;
                mProgressBackgroundBounds.top = mCenterY - mExpandCollapseValue;
                mProgressBackgroundBounds.right =  mCenterX + mRadius / 2 + mArrowLineToHorizontalLineAnimatedValue / 2;
                mProgressBackgroundBounds.bottom = mCenterY + mExpandCollapseValue;
                canvas.drawRoundRect(mProgressBackgroundBounds, 45, 45, mProgressBackgroundPaint);

                mProgressBounds.left = mCenterX - mRadius / 2 - mArrowLineToHorizontalLineAnimatedValue / 2;
                mProgressBounds.top = mCenterY - mExpandCollapseValue;
                mProgressBounds.right = mCenterX - mRadius / 2 - mArrowLineToHorizontalLineAnimatedValue / 2 + manualProgress * mCurrentGlobalManualProgressValue;
                mProgressBounds.bottom = mCenterY + mExpandCollapseValue;
                canvas.drawRoundRect(mProgressBounds, 45, 45, mProgressPaint);
                break;
            case ANIMATING_SUCCESS:
                mDrawingPaint.setStrokeWidth(mLineWidth);
                canvas.drawArc(mCircleBounds, new Arc(0, 360, false), mDrawingPaint);
                canvas.drawLine(
                        mCenterX - mRadius / 2 + mSuccessValue * 2 - mSuccessValue / (float) Math.sqrt(2f) / 2,
                        mCenterY + mSuccessValue,
                        mCenterX + mSuccessValue * 2 - mSuccessValue / (float) Math.sqrt(2f) / 2,
                        mCenterY - mSuccessValue,
                        mDrawingPaint
                );
                canvas.drawLine(
                        mCenterX - mSuccessValue - 2 * mSuccessValue / (float) Math.sqrt(2f) / 2,
                        mCenterY,
                        mCenterX + mRadius / 2 - mSuccessValue * 2 - mSuccessValue / (float) Math.sqrt(2f) / 2,
                        mCenterY + mSuccessValue,
                        mDrawingPaint
                );
                break;
            case ANIMATING_ERROR:
                mDrawingPaint.setStrokeWidth(mLineWidth);
                canvas.drawArc(mCircleBounds, new Arc(0, 360, false), mDrawingPaint);

                canvas.drawLine(
                        mCenterX - mRadius / 2 - mRadius / 4 + mErrorValue * 2,
                        mCenterY + mErrorValue,
                        mCenterX + mErrorValue,
                        mCenterY - mErrorValue,
                        mDrawingPaint
                );
                canvas.drawLine(
                        mCenterX - mErrorValue,
                        mCenterY - mErrorValue,
                        mCenterX + mRadius / 2 + mRadius / 4 - mErrorValue * 2,
                        mCenterY + mErrorValue,
                        mDrawingPaint
                );
                break;
        }
        if (mDotToProgressAnimatedValue > 0) {
            canvas.drawCircle(
                    mCenterX,
                    mCenterY - mDotToProgressAnimatedValue,
                    mStrokeWidth / 2,
                    mDrawingPaint
            );
        }

        if (mDotToProgressAnimation.isRunning() && !mArrowLineToHorizontalLine.isRunning()) {
            canvas.drawLine(
                    mCenterX - mRadius / 2 - mArrowLineToHorizontalLineAnimatedValue / 2,
                    mCenterY,
                    mCenterX + mRadius / 2 + mArrowLineToHorizontalLineAnimatedValue / 2,
                    mCenterY,
                    mDrawingPaint
            );
        }
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        setLayout();
        drawing(canvas);
    }

    public void playToSuccess() {
        mResultState = State.ANIMATING_SUCCESS;
        mWhichProgress = State.ANIMATING_PROGRESS;
        mArrowToLineAnimatorSet.start();
        invalidate();
    }

    public void playToError() {
        mWhichProgress = State.ANIMATING_PROGRESS;
        mResultState = State.ANIMATING_ERROR;
        mArrowToLineAnimatorSet.start();
        invalidate();
    }

    public void playManualProgressAnimation() {
        mWhichProgress = State.ANIMATING_MANUAL_PROGRESS;
        mResultState = State.ANIMATING_SUCCESS;
        mArrowToLineAnimatorSet.start();
        invalidate();
    }

    public void abortDownload() {
        if(mExpandAnimation.isRunning() || mProgressAnimation.isRunning()) {
            mProgressAnimationSet.cancel();
            mCollapseAnimation.start();
            invalidate();
        }
    }

    public void setErrorResultState() {
        if(mSuccessAnimation.isRunning() || mErrorAnimation.isRunning())
            return;
        mResultState = State.ANIMATING_ERROR;
    }

    public void setSuccessResultState() {
        if(mSuccessAnimation.isRunning() || mErrorAnimation.isRunning())
            return;
        mResultState = State.ANIMATING_SUCCESS;
    }

    public void setProgress(int value) {
        if(value < 1 || value > 100)
            return;
        mToArc = value * 3.6f;
        mManualProgressAnimation.setFloatValues(mFromArc, mToArc);
        mManualProgressAnimation.start();
        mFromArc = mToArc;
        invalidate();
    }

    public interface OnProgressUpdateListener {
        void onProgressUpdate(float currentPlayTime);

        void onStarted();

        void onEnded();

        void onAnimationSuccess();

        void onAnimationError();

        void onManualProgressStarted();

        void onManualProgressEnded();
    }

    public void setOnProgressUpdateListener(OnProgressUpdateListener listener) {
        mOnProgressUpdateListener = listener;
    }


}
